1 module hip.data.json;
2 
3 
4 JSONValue parseJSON(string jsonData)
5 {
6     return JSONValue.parse(jsonData);
7 }
8 struct JSONArray
9 {
10 	JSONValue[] value;
11 }
12 struct JSONObject
13 {
14 	JSONValue[string] value;
15 
16 	alias value this;
17 }
18 private enum JSONState
19 {
20 	key,
21 	lookingAssignment,
22 	lookingForNext,
23 	value
24 }
25 
26 enum JSONType
27 {
28 	bool_,
29 	float_,
30 	int_,
31 	string_,
32 	array,
33 	object
34 }
35 
36 bool isWhitespace(char ch){return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';}
37 bool isNumber(char ch){return ch >= '0' && ch <= '9';}
38 bool isNumeric(char ch){return (ch >= '0' && ch <= '9') || ch == '-' || ch == '.';}
39 
40 struct JSONValue
41 {
42 	union JSONData{
43 
44 		float _float;
45 		int _int;
46 		bool _bool;
47 		string _string;
48 		JSONObject* object;
49 		JSONArray* array;
50 	}
51 	JSONData data;
52 	string key;
53 	string error;
54 	JSONType type = JSONType.object;
55 
56     int integer() const {return get!int;}
57     bool boolean() const {return get!bool;}
58     string str() const {return get!string;}
59     ///Returns an array range.
60     auto array() const
61     {
62         assert(type == JSONType.array, "Tried to iterate a non array object of type "~getTypeName);
63         struct JSONValueArrayIterator
64         {
65             private const(JSONArray*) arr;
66             private size_t idx = 0;
67             size_t length(){return arr.value.length;}
68             bool empty(){return idx == arr.value.length;}
69             void popFront(){idx++;}
70             JSONValue front(){return arr.value[idx];}
71         }
72         return JSONValueArrayIterator(data.array);
73     }
74 
75     JSONValue object()
76     {
77         assert(type == JSONType.object, "Tried to get type object but value is of type "~getTypeName);
78         return JSONValue(data, key, error, JSONType.object);
79     }
80 
81 	string getTypeName() const
82 	{
83 		final switch(type) with(JSONType)
84 		{
85 			case int_: return "int";
86 			case bool_: return "bool";
87 			case float_: return "float";
88 			case string_: return "string";
89 			case object: return "object";
90 			case array: return "array";
91 		}
92 	}
93 
94 	T get(T)() const
95 	{
96 		static if(is(T == int))
97 		{
98 			assert(type == JSONType.int_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName);
99 			return data._int;
100 		}
101 		else static if(is(T == float))
102 		{
103 			assert(type == JSONType.float_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName);
104 			return data._float;
105 		}
106 		else static if(is(T == bool))
107 		{
108 			assert(type == JSONType.bool_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName);
109 			return data._bool;
110 		}
111 		else static if(is(T == string))
112 		{
113 			assert(type == JSONType.string_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName);
114 			return data._string;
115 		}
116 		else static if(is(T == JSONObject))
117 		{
118 			assert(type == JSONType.object, "Tried to get type "~T.stringof~" but value is of type "~getTypeName);
119 			return *data.object;
120 		}
121 		else static if(is(T == JSONArray))
122 		{
123 			assert(type == JSONType.array, "Tried to get type "~T.stringof~" but value is of type "~getTypeName);
124 			return *data.array;
125 		}
126 	}
127 	private static JSONValue create(T)(T data, string key)
128 	{
129 		JSONValue ret;
130 		ret.key = key;
131 		static if(is(T == int))
132 		{
133 			ret.type = JSONType.int_;
134 			ret.data._int = data;
135 		}
136 		else static if(is(T == float))
137 		{
138 			ret.type = JSONType.float_;
139 			ret.data._float = data;
140 		}
141 		else static if(is(T == bool))
142 		{
143 			ret.type = JSONType.bool_;
144 			ret.data._bool = data;
145 		}
146 		else static if(is(T == string))
147 		{
148 			ret.type = JSONType.string_;
149 			ret.data._string = data;
150 		}
151 		else static if(is(T == JSONObject*))
152 		{
153 			ret.type = JSONType.object;
154 			ret.data.object = data;
155 		}
156 		else static if(is(T == JSONArray*))
157 		{
158 			ret.type = JSONType.array;
159 			ret.data.array = data;
160 		}
161 		else static assert(false, "Unsupported type "~T.stringof);
162 		return ret;
163 	}
164 
165 
166 	private static JSONValue parse(string data)
167 	{
168 		import hip.util.conv:to;
169 		if(!data.length)
170 		{
171 			return JSONValue(JSONData.init, "", "No data provided");
172 		}
173 		ptrdiff_t index = 0;
174 		while(index < data.length && data[index++] != '{'){}
175 		if(index == data.length)
176 		{
177 			return JSONValue(JSONData.init, "", "Valid JSON starts with a '{'.");
178 		}
179 
180 		bool getNextString(string data, ptrdiff_t currentIndex, out ptrdiff_t newIndex, out string theString)
181 		{
182 			assert(data[currentIndex] == '"');
183 			newIndex = currentIndex+1;
184 			ptrdiff_t left = newIndex;
185 			while(newIndex < data.length && data[newIndex] != '"')
186 			{
187 				if(data[newIndex] == '\\')
188 				{
189 					theString~= data[left..newIndex];
190 					newIndex++;
191 					left = newIndex;
192 				}
193 				newIndex++;
194 			}
195 			if(newIndex == data.length) //Not found
196 				return false;
197 			theString~= data[left..newIndex]; //Assign output
198 			return true;
199 		}
200 
201 		bool getNextNumber(string data, ptrdiff_t currentIndex, out ptrdiff_t newIndex, out JSONData theData, out JSONType type)
202 		{
203 			assert(data[currentIndex].isNumeric);
204 			bool hasDecimal = false;
205 			newIndex = currentIndex;
206 			if(data[currentIndex] == '-')
207 				newIndex++;
208 			if(data[newIndex] == '.')
209 			{
210 				hasDecimal = true;
211 				newIndex++;
212 			}
213 
214 			while(newIndex < data.length)
215 			{
216 				if(!hasDecimal && data[newIndex] == '.')
217 				{
218 					if(!hasDecimal) hasDecimal = true;
219 					if(newIndex+1 < data.length) newIndex++;
220 				}
221 				if(isNumber(data[newIndex]))
222 					newIndex++;
223 				else 
224 					break;
225 			}
226 			if(hasDecimal)
227 			{
228 				theData._float = to!float(data[currentIndex..newIndex]);
229 				type = JSONType.float_;
230 			}
231 			else
232 			{
233 				theData._int = to!int(data[currentIndex..newIndex]);
234 				type = JSONType.int_;
235 			}
236 			//Stopped on a non number. Revert 1 step.
237 			newIndex--;
238 			return newIndex < data.length;
239 		}
240 		JSONValue ret;
241 		ret.data.object = new JSONObject();
242 		JSONValue* current = &ret;
243 		JSONState state = JSONState.lookingForNext;
244 		JSONValue lastValue = ret;
245 
246 		scope JSONValue[] stack = [ret];
247 		void pushNewScope(JSONValue val)
248 		{
249 			assert(val.type == JSONType.object || val.type == JSONType.array, "Unexpected push.");
250 			JSONValue* currTemp = current;
251 			stack~= val;
252 			current = &stack[$-1];
253 			if(currTemp.type == JSONType.object)
254 				currTemp.data.object.value[val.key] = *current;
255 			else
256 				currTemp.data.array.value~= *current;
257 		}
258 		void popScope()
259 		{
260 			assert(stack.length > 0, "Unexpected pop.");
261 			stack = stack[0..$-1];
262 			if(stack.length > 0)
263 			{
264 				current = &stack[$-1];
265 				assert(current.type == JSONType.object || current.type == JSONType.array, "Unexpected value in stack. (Typed "~(cast(size_t)(current.type)).to!string);
266 			}
267 		}
268 
269 		void pushToStack(JSONValue val)
270 		{
271 			switch(current.type) with(JSONType)
272 			{
273 				case object:
274 					current.data.object.value[val.key] = val;
275 					break;
276 				case array:
277 					current.data.array.value~= val;
278 					break;
279 				default: assert(false, "Unexpected stack type: "~current.getTypeName);
280 			}
281 			lastValue = val;
282 		}
283 
284 		size_t line = 0;
285 		string getErr(string err="")
286 		{
287 			return "Error at line "~line.to!string~" "~err~" on index '"~index.to!string~"' last parsed: "~lastValue.toString;
288 		}
289 
290 		string lastKey;
291 		do
292 		{
293 			char ch = data[index];
294 			switch(ch)
295 			{
296 				case '\n': 
297 					line++;
298 					break;
299 				case '{':
300 				{
301 					if(state != JSONState.value)
302 						return JSONValue(JSONData.init, "", getErr());
303 					JSONValue obj = JSONValue.create(new JSONObject(), lastKey);
304 					pushNewScope(obj);
305 
306 					state = JSONState.key;
307 					break;
308 				}
309 				case '}':
310 					popScope();
311 					state = JSONState.lookingForNext;
312 					break;
313 				case ':':
314 					if(state != JSONState.lookingAssignment)
315 						return JSONValue(JSONData.init, "", getErr("expected key before ':'"));
316 					state = JSONState.value;
317 					break;
318 				case '"':
319 				{
320 
321 					switch(state)
322 					{
323 						case JSONState.lookingForNext:
324 							if(current.type == JSONType.object)
325 								goto case JSONState.key;
326 							else if(current.type == JSONType.array)
327 								goto case JSONState.value;
328 							goto default;
329 						case JSONState.key:
330 						{
331 							assert(current.type == JSONType.object, getErr("only object can receive a key."));
332 							if(!getNextString(data, index, index, lastKey))
333 								return JSONValue(JSONData.init, "", getErr("unclosed quotes."));
334 							pushToStack(JSONValue(JSONData.init, lastKey));
335 							state = JSONState.lookingAssignment;
336 							break;
337 						}
338 						case JSONState.value:
339 						{
340 							string val;
341 							if(!getNextString(data, index, index, val))
342 								return JSONValue(JSONData.init, "", getErr("unclosed quotes."));
343 							pushToStack(JSONValue.create!string(val, lastKey));
344 							state = JSONState.lookingForNext;
345 							break;
346 						}
347 						default:
348 							return JSONValue(JSONData.init, "", getErr("comma expected before key "~lastValue.key));
349 					}
350 					break;
351 				}
352 				case '[':
353 				{
354 					if(state != JSONState.lookingForNext && state != JSONState.value)
355 						return JSONValue(JSONData.init, "", getErr(" expected to be a value. "));
356 					pushNewScope(JSONValue.create(new JSONArray(), lastKey));
357 					state = JSONState.value;
358 					break;
359 				}
360 				case ']':
361 					if(state != JSONState.lookingForNext && state != JSONState.value)
362 						return JSONValue(JSONData.init, "", getErr("expected to be a value. "));
363 					popScope();
364 					state = JSONState.lookingForNext;
365 					break;
366 				case ',':
367 					if(state != JSONState.lookingForNext)
368 						return JSONValue(JSONData.init, "", getErr("unexpected comma. "));
369 					if(current.type != JSONType.object && current.type != JSONType.array)
370 						return JSONValue(JSONData.init, "", getErr("unexpected comma. "));
371 
372 					switch(current.type) with(JSONType)
373 					{
374 						case object: state = JSONState.key; break;
375 						case array: state = JSONState.lookingForNext; break;
376 						default: assert(false, "Error?");
377 					}
378 					break;
379 				default:
380 					switch(state)
381 					{
382 						case JSONState.value: //Any value
383 							if(ch.isNumeric)
384 							{
385 								if(!getNextNumber(data, index, index, lastValue.data, lastValue.type))
386 									return JSONValue(JSONData.init, "", getErr("unexpected end of file."));
387 								pushToStack(lastValue);
388 								state = JSONState.lookingForNext;
389 							}
390 							else if(index + "true".length < data.length && data[index.."true".length + index] == "true")
391 							{
392 								pushToStack(JSONValue.create!bool(true, lastKey));
393 								state = JSONState.lookingForNext;
394 							}
395 							else if(index + "false".length < data.length && data[index.."false".length + index] == "false")
396 							{
397 								pushToStack(JSONValue.create!bool(false, lastKey));
398 								state = JSONState.lookingForNext;
399 							}
400 
401 							break;
402 						case JSONState.lookingForNext: //Array
403 							if(ch.isNumeric)
404 							{
405 								if(current.type != JSONType.array)
406 									return JSONValue(JSONData.init, "", getErr("unexpected number."));
407 								if(!getNextNumber(data, index, index, lastValue.data, lastValue.type))
408 									return JSONValue(JSONData.init, "", getErr("unexpected end of file."));
409 								pushToStack(lastValue);
410 								state = JSONState.lookingForNext;
411 							}
412 							else if(index + "true".length < data.length && data[index.."true".length + index] == "true")
413 							{
414 								if(current.type != JSONType.array)
415 									return JSONValue(JSONData.init, "", getErr("unexpected number."));
416 								pushToStack(JSONValue.create!bool(true, lastKey));
417 								state = JSONState.lookingForNext;
418 							}
419 							else if(index + "false".length < data.length && data[index.."false".length + index] == "false")
420 							{
421 								if(current.type != JSONType.array)
422 									return JSONValue(JSONData.init, "", getErr("unexpected number."));
423 								pushToStack(JSONValue.create!bool(false, lastKey));
424 								state = JSONState.lookingForNext;
425 							}
426 							break;
427 						default:break;
428 					}
429 					break;
430 			}
431 			index++;
432 		}
433 		while(index < data.length && stack.length > 0);
434 		return ret;
435 	}
436 
437 	JSONValue opIndex(string key) const
438 	{
439 		assert(type == JSONType.object, "Can't get a member from a non object.");
440 		return (*data.object)[key];
441 	}
442 	const(JSONValue)* opBinaryRight(string op)(string key) const
443 	if(op == "in")
444 	{
445 		if(type != JSONType.object)	return null;
446 		return key in *(data.object).value;
447 	}
448     JSONValue* opBinaryRight(string op)(string key)
449 	if(op == "in")
450 	{
451 		if(type != JSONType.object)	return null;
452 		return key in (*data.object).value;
453 	}
454 
455     int opApply(scope int delegate(string key, JSONValue v) dg)
456     {
457         if(type != JSONType.object)
458         {
459             assert(false, "Can't iterate with key[string] and value[JSONValue] an object of type "~getTypeName);
460         }
461         int result = 0;
462         foreach (k, v ; data.object.value)
463         {
464             result = dg(k, v);
465             if (result)
466                 break;
467         }
468     
469         return result;
470     }
471 	bool hasErrorOccurred(){ return error.length != 0; }
472 
473 	//selfPrintKey is only used for object.
474 	string toString(bool selfPrintkey = true)
475 	{
476 		if(hasErrorOccurred)
477 			return error;
478 		import hip.util.conv:to;
479 		string ret;
480 		final switch ( type ) with(JSONType)
481 		{
482 			case int_:
483 				ret = data._int.to!string;
484 				break;
485 			case float_:
486 				ret = data._float.to!string;
487 				break;
488 			case bool_:
489 				ret = data._bool ? "true" : "false";
490 				break;
491 			case string_:
492 				ret = '"'~data._string~'"';
493 				break;
494 			case array:
495 			{
496 				ret = "[";
497 				bool isFirst = true;
498 				foreach(v; data.array.value)
499 				{
500 					if(!isFirst)
501 						ret~=", ";
502 					isFirst = false;
503 					ret~= v.toString;
504 				}
505 				ret~= "]";
506 				break;
507 			}
508 			case object:
509 			{
510 				if(selfPrintkey)
511 				{
512 					ret = '"'~key~"\": ";
513 				}
514 				ret~= '{';
515 				bool isFirst = true;
516 				foreach(k, v; data.object.value)
517 				{
518 					if(!isFirst)
519 						ret~= ", ";
520 					isFirst = false;
521 					ret~= '"'~k~"\" : "~v.toString(false);
522 				}
523 				ret~= '}';
524 				break;
525 
526 			}
527 		}
528 		return ret;
529 	}
530 
531     void dispose()
532     {
533         if(type == JSONType.object)
534         {
535             foreach(v; data.object.value)
536                 v.dispose();
537         }
538         else if(type == JSONType.array)
539         {
540             foreach(v; data.array.value)
541                 v.dispose();
542         }
543         
544     }
545 }